package Entities;
import heuristics.IHeuristic;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class Calendar implements ICalendar {
	private static List<Course> calendarDB ;
	private static int averageGrade = 0;
	private static int averageWorkload = 0;
	
	private static IHeuristic heuristic;
	private static Random rand = new Random();
	
	private List<Course> chosenCourses;
	private List<Course> remainingCourses;
	private int adjacentIndex;
	private int numberOfCollisions;
	
	private double heuristicValue;
	
	// C'tor
	public Calendar(List<Course> chosenCourses) {
		// heuristic check
		if (heuristic == null){
			throw new IllegalArgumentException("Heuristic is not set!!!");
		}
		
		if ((chosenCourses == null) || (chosenCourses.isEmpty())){ // empty calendar
			this.chosenCourses = new LinkedList<Course>();
			remainingCourses = calendarDB; // reference to a list that wouldn't change 
		}
		else{
			this.chosenCourses = new LinkedList<Course>(chosenCourses);
			
			// generating remainninCourses as a substraction of chosen courses from calendarDB
			remainingCourses = new LinkedList<Course>(calendarDB);
			remainingCourses.removeAll(chosenCourses);
		}
		
		calcNumberOfCollisions();
		adjacentIndex = 0;
		heuristicValue = heuristic.evaluate(this);
	}
	
	//Private functions
	private void calcNumberOfCollisions(){
		boolean[] times = new boolean[Course.timeSlot];
		// init array
		Arrays.fill(times, false);
		
		
		for (Course course : chosenCourses) {
			int currTime = course.getTime();
			if (times[currTime] == true){
				numberOfCollisions++;
			}
			else{
				times[currTime] = true;				
			}
		}
	}

	// Static functions
	public static void init(List<Course> input){
		calendarDB = new LinkedList<Course>(input);
		for (Course course : calendarDB) {
			averageGrade += course.getAverageGrade();
			averageWorkload += course.getWorkLoad();	
		}
		averageGrade /= calendarDB.size();
		averageWorkload /= calendarDB.size();
	}

	public static void setHeuristic(IHeuristic toSet){
		if (toSet != null){			
			heuristic = toSet;
		}
	}

	public static Calendar rand(int maxNumberOfCourses) {
		List<Course> res = new LinkedList<Course>();
		List<Course> remainingCourses = new LinkedList<Course>(calendarDB);
		for (int i = 0; i < maxNumberOfCourses; i++) {
			res.add(remainingCourses.remove(rand.nextInt(remainingCourses.size())));
		}
		
		Calendar cal = new Calendar(res);
		return cal;
	}
		
	public boolean isBetter(Calendar toCompare){
		if (toCompare == null){
			throw new IllegalArgumentException();
		}
		return (this.heuristicValue > toCompare.getHeuristicValue());
	}
	
	// Methods
	public double getHeuristicValue() {
		return heuristicValue;
	}

	public final List<Course> getRemainingCourses(){
		return remainingCourses;
	}
	
	public int numberOfCourses(){
		return chosenCourses.size();
	}
	
	public String toString()
	{
		String res = "Printing Calendar: \n";
		if (chosenCourses.isEmpty()){
			res += "Empty Calendar";
		}
		else{
			res += "Total number of courses: " + chosenCourses.size() + "\n";
			res += "Calendar evaluation: " + heuristicValue + "\n";
			for (Course courseInCalendar: chosenCourses) {
				res += courseInCalendar.toString();
			}
		}
		return res;
	}

	// Overrides
	@Override
	public Calendar getNextAdjacentCalendar() {
		int numberOfRemainingCourses = remainingCourses.size();
		if (adjacentIndex == calendarDB.size()){
			adjacentIndex = 0;
			return null;
		}
		List<Course> adjacentCourses = new LinkedList<Course>(chosenCourses);
		
		// adding next remainnin course
		if (adjacentIndex < numberOfRemainingCourses){			
			Course nextCourse = remainingCourses.get(adjacentIndex);
			adjacentCourses.add(nextCourse);
		}
		else{ // now we remove course from the list
			adjacentCourses.remove(chosenCourses.get(adjacentIndex - numberOfRemainingCourses));			
		}
		adjacentIndex++;
		
		return new Calendar(adjacentCourses);
	}

	@Override
	public final List<Course> getCourses() {
		return chosenCourses;
	}

	@Override
	public int getNumberOfCollisions() {
		return numberOfCollisions;
	}

	@Override
	public boolean isEmpty() {
		return chosenCourses.isEmpty();
	}

	@Override
	public final List<Course> getCoursesWithoutAdjacents() {
		List<Course> res = new LinkedList<Course>();
		for (Course course : chosenCourses) {
			List<Integer> courseAdjacentsIds = course.getAdjacentsIds();
			for (Integer id : courseAdjacentsIds) {
				if ((!isCourseInCalendar(id)) && !res.contains(course)){
					res.add(course);
					break;
				}
			}
		}	
		return res;
	}

	/**
	 * 	private functions
	 */
	private boolean isCourseInCalendar(int courseId){
		for (Course course : chosenCourses) {
			if (course.getId() == courseId){
				return true;
			}
		}
		return false;
	}

	/**
	 * This function returns random neighbour calendar to the current calendar 
	 * @return
	 */
	public Calendar getRandomNeighbour(){
		int neighbourNumber = rand.nextInt(calendarDB.size());
		List<Course> courses = new LinkedList<Course>(chosenCourses);		
		if (neighbourNumber < courses.size()){
			// remove course from chosenCourses
			courses.remove(neighbourNumber);
		}
		else{
			courses.add(this.remainingCourses.get(neighbourNumber - courses.size()));
		}
		return new Calendar(courses);
	}
	public static IHeuristic getHeuristic() {
		return heuristic;
	}

	public static int getAverageGrade() {
		return averageGrade;
	}
	public static int getAverageWorkload() {
		return averageWorkload;
	}

	public boolean isSame(Calendar bestNeighbour) {
		return (heuristicValue == bestNeighbour.getHeuristicValue()); 
	}
	public static void initSeed(){
		rand.setSeed(rand.nextLong());
	}
}
